﻿#include "histogram.h"
#include <QDebug>
#include <random>

Histogram1D::Histogram1D() 
{
    // Prepare default arguments for 1D histogram
    histSize[0] = 256;   // 256 bins
    hranges[0] = 0.0;    // from 0 (inclusive)
    hranges[1] = 256.0;  // to 256 (exclusive)
    ranges[0] = hranges; 
    channels[0] = 0;     // we look at channel 0
}

void Histogram1D::setChannel(int c) 
{
    channels[0] = c;
}

int Histogram1D::getChannel() 
{

    return channels[0];
}
    
void Histogram1D::setRange(float minValue, float maxValue) 
{
    hranges[0] = minValue;
    hranges[1] = maxValue;
}

float Histogram1D::getMinValue() 
{
    return hranges[0];
}

float Histogram1D::getMaxValue() 
{
    return hranges[1];
}

void Histogram1D::setNBins(int nbins) 
{
    histSize[0] = nbins;
}

// Gets the number of bins in histogram.
int Histogram1D::getNBins() 
{
    return histSize[0];
}

cv::Mat Histogram1D::stretch(const cv::Mat &image, int minValue) 
{
    // Compute histogram first
    cv::Mat hist = getHistogram(image);

    // find left extremity of the histogram
    int imin = 0;
    for (; imin < histSize[0]; imin++) {
        // ignore bins with less than minValue entries
        if (hist.at<float>(imin) > minValue)
            break;
    }

    // find right extremity of the histogram
    int imax = histSize[0] - 1;
    for (; imax >= 0; imax--) {

        // ignore bins with less than minValue entries
        if (hist.at<float>(imax) > minValue)
            break;
    }

    // Create lookup table
    int dims[1] = { 256 };
    cv::Mat lookup(1, dims, CV_8U);

    for (int i = 0; i< 256; i++) {

        if (i < imin) lookup.at<uchar>(i) = 0;
        else if (i > imax) lookup.at<uchar>(i) = 255;
        else lookup.at<uchar>(i) = cvRound(255.0*(i - imin) / (imax - imin));
    }

    // Apply lookup table
    cv::Mat result;
    result = applyLookUp(image, lookup);

    return result;
}
    
cv::Mat Histogram1D::getHistogram(const cv::Mat &image) 
{
    cv::Mat hist;

    // Compute 1D histogram with calcHist
    cv::calcHist(&image, 
        1,			// histogram of 1 image only
        channels,	// the channel used
        cv::Mat(),	// no mask is used
        hist,		// the resulting histogram
        1,			// it is a 1D histogram
        histSize,	// number of bins
        ranges		// pixel value range
    );

    return hist;
}

cv::Mat Histogram1D::getHistogramImage(const cv::Mat &image, int zoom)
{
    // Compute histogram first
    cv::Mat hist = getHistogram(image);

    // Creates image
    return Histogram1D::getImageOfHistogram(hist, zoom);
}

// Create an image representing a histogram
cv::Mat Histogram1D::getImageOfHistogram(const cv::Mat &hist, int zoom)
{
    // Get min and max bin values
    double maxVal = 0;
    double minVal = 0;
    cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);

    // get histogram size
    int histSize = hist.rows;

    // Square image on which to display histogram
    cv::Mat histImg(histSize*zoom, histSize*zoom, CV_8U, cv::Scalar(255));

    // set highest point at 90% of nbins (i.e. image height)
    int hpt = static_cast<int>(0.9*histSize);

    // Draw vertical line for each bin
    for (int h = 0; h < histSize; h++) {

        float binVal = hist.at<float>(h);
        if (binVal>0) {
            int intensity = static_cast<int>(binVal*hpt / maxVal);
            cv::line(histImg, cv::Point(h*zoom, histSize*zoom),
                cv::Point(h*zoom, (histSize - intensity)*zoom), cv::Scalar(0), zoom);
        }
    }

    return histImg;
}


cv::Mat Histogram1D::stretch(const cv::Mat &image, double percent)
{
    return stretch(image, percent, percent);
}

cv::Mat Histogram1D::stretch(const cv::Mat &image, double percent1, double percent2)
{
    cv::MatND hist = getHistogram(image);
    int imin, imax;
    if(percent1 < 0.0) percent1 = 0.0;
    if(percent1 > 1.0) percent1 = 1.0;
    percent1 = image.rows * image.cols * percent1;
    double value = 0;
    for(imin = 0; imin < histSize[0]; imin++)
    {
        value += hist.at<float>(imin);
        if(value > percent1) break;
    }

    value = 0;
    if(percent2 < 0.0) percent2 = 0.0;
    if(percent2 > 1.0) percent2 = 1.0;
    percent2 = image.rows * image.cols * percent2;
    for(imax = histSize[0] - 1; imax >= 0; imax--)
    {
        value += hist.at<float>(imax);
        if(value > percent2) break;
    }
    //int dim = 256;
    cv::Mat lookup(1, 256, CV_8U);

    for(int i = 0; i < 256; i++)
    {
        if(i < imin) lookup.at<uchar>(i) = 0;
        else if(i > imax) lookup.at<uchar>(i) = 255;
        else
        {
            int v = 255 * (i - imin) / (imax - imin);
            lookup.at<uchar>(i) = static_cast<uchar>(v);
        }
    }
    cv::Mat ret;
    cv::LUT(image, lookup, ret);
    return ret;
}

cv::Mat Histogram1D::equalize(const cv::Mat &image)
{
    cv::Mat result;
    cv::equalizeHist(image,result);

    return result;
}

cv::Mat Histogram1D::applyLookUp(const cv::Mat& image, const cv::Mat& lookup)
{
    // the output image
    cv::Mat result;
    // apply lookup table
    cv::LUT(image,lookup,result);

    return result;
}

cv::Mat Histogram1D::applyLookUpWithIterator(const cv::Mat& image, const cv::Mat& lookup)
{
    // Set output image (always 1-channel)
    cv::Mat result(image.rows, image.cols, CV_8U);
    cv::Mat_<uchar>::iterator itr = result.begin<uchar>();

    // Iterates over the input image
    cv::Mat_<uchar>::const_iterator it = image.begin<uchar>();
    cv::Mat_<uchar>::const_iterator itend = image.end<uchar>();

    // Applies lookup to each pixel
    for ( ; it!= itend; ++it, ++itr) {

        *itr = lookup.at<uchar>(*it);
    }

    return result;
}
    
cv::Mat Histogram1D::normalize(const cv::Mat &image)
{
    // Compute histogram first
    cv::MatND hist = getHistogram(image);
    int imin, imax;
    for(imin = 0; imin < histSize[0]; imin++)
    {
        if(hist.at<float>(imin) > 0) break;
    }

    for(imax = histSize[0] - 1; imax >= 0; imax--)
    {
        if(hist.at<float>(imax) > 0) break;
    }

    cv::Mat lookup(1, 256, CV_8U);

    for(int i = 0; i < 256; i++)
    {
        if(i < imin) lookup.at<uchar>(i) = 0;
        else if(i > imax) lookup.at<uchar>(i) = 255;
        else
        {
            int v = 255 * (i - imin) / (imax - imin);
            lookup.at<uchar>(i) = static_cast<uchar>(v);
        }
    }
    cv::Mat ret;
    cv::LUT(image, lookup, ret);
    return ret;
}


ColorHistogram::ColorHistogram() 
{
    // Prepare default arguments for a color histogram
    // each dimension has equal size and range
    histSize[0] = histSize[1] = histSize[2] = 256;
    hranges[0] = 0.0;    // BRG range from 0 to 256
    hranges[1] = 256.0;
    ranges[0] = hranges; // in this class,  
    ranges[1] = hranges; // all channels have the same range
    ranges[2] = hranges; 
    channels[0] = 0;		// the three channels: B
    channels[1] = 1;     // G
    channels[2] = 2;     // R
}

void ColorHistogram::setSize(int size) 
{
    // each dimension has equal size 
    histSize[0] = histSize[1] = histSize[2] = size;
}

cv::Mat ColorHistogram::getHistogram(const cv::Mat &image) 
{
    cv::Mat hist;
    // BGR color histogram
    hranges[0] = 0.0;    // BRG range
    hranges[1] = 256.0;
    channels[0] = 0;		// the three channels 
    channels[1] = 1; 
    channels[2] = 2; 

    // Compute histogram
    cv::calcHist(&image, 
        1,			// histogram of 1 image only
        channels,	// the channel used
        cv::Mat(),	// no mask is used
        hist,		// the resulting histogram
        3,			// it is a 3D histogram
        histSize,	// number of bins
        ranges		// pixel value range
    );
    return hist;
}

cv::SparseMat ColorHistogram::getSparseHistogram(const cv::Mat &image) 
{
    cv::SparseMat hist(3,        // number of dimensions
                       histSize, // size of each dimension
                       CV_32F);

    // BGR color histogram
    hranges[0]= 0.0;    // BRG range
    hranges[1]= 256.0;
    channels[0]= 0;		// the three channels 
    channels[1]= 1; 
    channels[2]= 2; 

    // Compute histogram
    cv::calcHist(&image, 
        1,			// histogram of 1 image only
        channels,	// the channel used
        cv::Mat(),	// no mask is used
        hist,		// the resulting histogram
        3,			// it is a 3D histogram
        histSize,	// number of bins
        ranges		// pixel value range
    );
    return hist;
}

cv::Mat ColorHistogram::getHueHistogram(const cv::Mat &image, int minSaturation) 
{
    cv::Mat hist;

    // Convert to HSV colour space
    cv::Mat hsv;
    cv::cvtColor(image, hsv, CV_BGR2HSV);

    // Mask to be used (or not)
    cv::Mat mask;
    // creating the mask if required
    if (minSaturation>0) {
    
        // Spliting the 3 channels into 3 images
        std::vector<cv::Mat> v;
        cv::split(hsv,v);

        // Mask out the low saturated pixels
        cv::threshold(v[1],mask,minSaturation,255,
                             cv::THRESH_BINARY);
    }

    // Prepare arguments for a 1D hue histogram
    hranges[0]= 0.0;    // range is from 0 to 180
    hranges[1]= 180.0;
    channels[0]= 0;    // the hue channel 

    // Compute histogram
    cv::calcHist(&hsv, 
        1,			// histogram of 1 image only
        channels,	// the channel used
        mask,		// binary mask
        hist,		// the resulting histogram
        1,			// it is a 1D histogram
        histSize,	// number of bins
        ranges		// pixel value range
    );

    return hist;
}

cv::Mat ColorHistogram::getabHistogram(const cv::Mat &image) 
{
    cv::Mat hist;

    // Convert to Lab color space
    cv::Mat lab;
    cv::cvtColor(image, lab, CV_BGR2Lab);

    // Prepare arguments for a 2D color histogram
    hranges[0] = 0;
    hranges[1] = 256.0;
    channels[0] = 1; // the two channels used are ab 
    channels[1] = 2; 

    // Compute histogram
    cv::calcHist(&lab, 
        1,			// histogram of 1 image only
        channels,	// the channel used
        cv::Mat(),	// no mask is used
        hist,		// the resulting histogram
        2,			// it is a 2D histogram
        histSize,	// number of bins
        ranges		// pixel value range
    );

    return hist;
}

ContentFinder::ContentFinder() : threshold(0.1f), isSparse(false)
{
    // in this class,
    // all channels have the same range
    ranges[0]= hranges;
    ranges[1]= hranges;
    ranges[2]= hranges;
}

void ContentFinder::setThreshold(float t)
{
    threshold = t;
}

// Gets the threshold
float ContentFinder::getThreshold()
{
    return threshold;
}

// Sets the reference histogram
void ContentFinder::setHistogram(const cv::Mat& h)
{
    isSparse = false;
    cv::normalize(h,histogram,1.0);
}

void ContentFinder::setHistogram(const cv::SparseMat& h)
{
    isSparse = true;
    cv::normalize(h, shistogram, 1.0, cv::NORM_L2);
}

// Simplified version in which
// all channels used, with range [0,256[
cv::Mat ContentFinder::find(const cv::Mat& image)
{
    cv::Mat result;

    hranges[0] = 0.0;	// default range [0,256[
    hranges[1] = 256.0;
    channels[0] = 0;		// the three channels
    channels[1] = 1;
    channels[2] = 2;

    return find(image, hranges[0], hranges[1], channels);
}

cv::Mat ContentFinder::find(const cv::Mat& image, float minValue, float maxValue, int *channels)
{
    cv::Mat result;
    hranges[0] = minValue;
    hranges[1] = maxValue;
    if (isSparse)
    { // call the right function based on histogram type

       for (int i = 0; i < shistogram.dims(); i++)
       {
          this->channels[i] = channels[i];
       }

       cv::calcBackProject(&image,
                  1,            // we only use one image at a time
                  channels,     // vector specifying what histogram dimensions belong to what image channels
                  shistogram,   // the histogram we are using
                  result,       // the resulting back projection image
                  ranges,       // the range of values, for each dimension
                  255.0         // the scaling factor is chosen such that a histogram value of 1 maps to 255
       );

    }
    else
    {
       for (int i = 0; i < histogram.dims; i++)
       {
          this->channels[i] = channels[i];
       }

       cv::calcBackProject(&image,
                  1,            // we only use one image at a time
                  channels,     // vector specifying what histogram dimensions belong to what image channels
                  histogram,    // the histogram we are using
                  result,       // the resulting back projection image
                  ranges,       // the range of values, for each dimension
                  255.0         // the scaling factor is chosen such that a histogram value of 1 maps to 255
       );
    }

    // Threshold back projection to obtain a binary image
    if (threshold > 0.0)
    {
        cv::threshold(result, result, 255.0 * threshold, 255.0, cv::THRESH_BINARY);
    }
    return result;
}

ImageComparator::ImageComparator() :nBins(8) {}

// Set number of bins used when comparing the histograms
void ImageComparator::setNumberOfBins( int bins)
{
    nBins= bins;
}

int ImageComparator::getNumberOfBins()
{
    return nBins;
}

// set and compute histogram of reference image
void ImageComparator::setReferenceImage(const cv::Mat& image)
{
    hist.setSize(nBins);
    refH= hist.getHistogram(image);
}

// compare the image using their BGR histograms
double ImageComparator::compare(const cv::Mat& image)
{
    inputH= hist.getHistogram(image);
    // histogram comparison using intersection
    return cv::compareHist(refH,inputH, cv::HISTCMP_INTERSECT);
}
